package com.wlyuan.open.scanner.repository.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wlyuan.open.constants.CustomerTypeEnum;
import com.wlyuan.open.scanner.dao.entity.BalanceBillindexDO;
import com.wlyuan.open.scanner.dao.entity.BalanceCheckDetailsDO;
import com.wlyuan.open.scanner.dao.entity.OrderIndexDO;
import com.wlyuan.open.scanner.dao.entity.PayDO;
import com.wlyuan.open.scanner.dao.mapper.BalanceBillindexMapper;
import com.wlyuan.open.scanner.dao.mapper.BalanceCheckDetailsMapper;
import com.wlyuan.open.scanner.dao.mapper.OrderIndexMapper;
import com.wlyuan.open.scanner.dao.mapper.PayMapper;
import com.wlyuan.open.scanner.dao.write.PayWriteMapper;
import com.wlyuan.open.scanner.repository.PayRepository;
import com.wlyuan.open.scanner.utils.BatchSelectUtils;
import com.wlyuan.open.scanner.utils.StreamUtils;
import com.wlyuan.open.scanner.utils.enums.bill.BillTypeEnum;
import com.wlyuan.open.scanner.utils.enums.bill.PayTypeEnum;
import com.wlyuan.open.scanner.utils.enums.order.OrderSrcClassEnum;
import com.wlyuan.open.scanner.utils.enums.order.OrderStatusEnum;
import com.wlyuan.open.utils.LongUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lijing
 * @description
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PayRepositoryImpl implements PayRepository {
    private final PayMapper payMapper;
    private final OrderIndexMapper orderIndexMapper;
    private final BalanceBillindexMapper balanceBillindexMapper;
    private final BalanceCheckDetailsMapper balanceCheckDetailsMapper;
    @Resource(name = "writeSqlSessionTemplate")
    private SqlSessionTemplate sqlSessionTemplate;

    @Override
    public void init() {
        var result = getResult();
        var payList = result.stream().map(data -> {
            if (PayTypeEnum.isReturnPay(data.getCheckDetailsertype())) {
                //回执付
                return bill(data.getCheckDetailid(), data.getCheckDetailownerid(), data.getCheckDetailcompanyid(), data.getCheckDetailbilltime());
            } else {
                //预付、到付、代收
                return order(data.getCheckDetailid(), data.getCheckDetailorderid(), data.getCheckDetailsertype(), data.getCheckDetailcompanyid(), data.getCheckDetailbilltime());
            }
        }).collect(Collectors.toList());
        logger.info("初始化数据大小：{}", payList.size());
        payList = payList.parallelStream().filter(Objects::nonNull).collect(Collectors.toList());
        logger.info("初始化数据大小过滤空：{}", payList.size());
        payList = distinctUpAndDown(payList);
        logger.info("根据up 和 down 去重：{}", payList.size());
        //批量插入
        batch(payList.stream().sorted(Comparator.comparing(PayDO::getCreateTime)).collect(Collectors.toList()));
        logger.info("初始化收付款结束......");
    }

    @Override
    public void initTime(String startTime, String endTime) {
        var result = getResult(startTime, endTime);
        var payList = result.stream().map(data -> {
            if (PayTypeEnum.isReturnPay(data.getCheckDetailsertype())) {
                //回执付
                return bill(data.getCheckDetailid(), data.getCheckDetailownerid(), data.getCheckDetailcompanyid(), data.getCheckDetailbilltime());
            } else {
                //预付、到付、代收
                return order(data.getCheckDetailid(), data.getCheckDetailorderid(), data.getCheckDetailsertype(), data.getCheckDetailcompanyid(), data.getCheckDetailbilltime());
            }
        }).collect(Collectors.toList());
        logger.info("初始化数据大小：{}", payList.size());
        payList = payList.stream().filter(Objects::nonNull).collect(Collectors.toList());
        logger.info("初始化数据大小过滤空：{}", payList.size());
        payList = distinctUpAndDown(payList);
        logger.info("根据up 和 down 去重：{}", payList.size());
        //批量插入
        batch(payList.stream().sorted(Comparator.comparing(PayDO::getCreateTime)).collect(Collectors.toList()));
        logger.info("初始化收付款结束......");
    }

    @Override
    public void save(Long checkId, Long checkOwnerId, Long checkOrderId, Long tenantId, Integer type) {
        if (PayTypeEnum.isReturnPay(type)) {
            batch(Arrays.asList(bill(checkId, checkOwnerId, tenantId, null)));
        } else {
            batch(Arrays.asList(order(checkId, checkOrderId, type, tenantId, null)));
        }
    }

    private List<BalanceCheckDetailsDO> getResult() {
        List<BalanceCheckDetailsDO> result = new ArrayList<>();
        //获取回执付
        var list = balanceCheckDetailsMapper.getReturnPayList();
        list = StreamUtils.maxByGroup(list, BalanceCheckDetailsDO::getCheckDetailownerid, Comparator.comparing(BalanceCheckDetailsDO::getCheckDetailid)).values().stream().collect(Collectors.toList());
        result.addAll(list);
        //获取预付、到付、代收
        var noList = balanceCheckDetailsMapper.getNoReturnList();
        result.addAll(noList);
        StreamUtils.sortList(result, false, BalanceCheckDetailsDO::getCheckDetailid);
        logger.info("初始化数据大小：{}", result.size());
        return result;
    }

    private List<BalanceCheckDetailsDO> getResult(String startTime, String endTime) {
        List<BalanceCheckDetailsDO> result = new ArrayList<>();
        //获取回执付
        var list = balanceCheckDetailsMapper.getReturnPayListTime(startTime, endTime);
        result.addAll(list);
        //获取预付、到付、代收
        var noList = balanceCheckDetailsMapper.getNoReturnListTime(startTime, endTime);
        result.addAll(noList);
        StreamUtils.sortList(result, false, BalanceCheckDetailsDO::getCheckDetailid);
        logger.info("初始化数据大小：{}", result.size());
        return result;
    }

    private PayDO bill(Long checkId, Long billId, Long tenantId, LocalDateTime createTime) {
        var bill = balanceBillindexMapper.getById(billId);
        return Optional.ofNullable(bill).map(data -> {
            //是否应付
            if (BillTypeEnum.isPayable(data.getIndexBilltype().intValue())) {
                //线上承运方 up = 自己 down = srcId对应的checkId
                if (LongUtils.notNullLong(data.getIndexSupplierid())) {
                    return PayDO.create(checkId, billUpOrDown(data.getIndexSrcid(), billId), PayTypeEnum.RETURNPAY.getValue(), tenantId, createTime);
                }
                //线下承运方、自营 up = 自己 down = null
                if (LongUtils.notNullLong(data.getIndexSuppliersymbolid()) || LongUtils.notNullLong(data.getIndexDriverid())) {
                    return PayDO.create(checkId, 0L, PayTypeEnum.RETURNPAY.getValue(), tenantId, createTime);
                }
            }
            //应收
            if (BillTypeEnum.isReceivable(data.getIndexBilltype().intValue())) {
                //线上客户  up = srcId对应的checkId down = 自己
                if (CustomerTypeEnum.isOnline(data.getCustomerType())) {
                    return PayDO.create(billUpOrDown(data.getIndexSrcid(), billId), checkId, PayTypeEnum.RETURNPAY.getValue(), tenantId, createTime);
                }
                //线下客户，自己 up = null down = 自己
                if (CustomerTypeEnum.isOffline(data.getCustomerType()) || CustomerTypeEnum.isMySelf(data.getCustomerType())) {
                    return PayDO.create(0L, checkId, PayTypeEnum.RETURNPAY.getValue(), tenantId, createTime);
                }
            }
            return null;
        }).orElse(null);
    }

    private Long billUpOrDown(Long billSrcId, Long billId) {
        //来源id是否为空
        var ownerId = LongUtils.notNullLong(billSrcId) ? billSrcId : Optional.ofNullable(balanceBillindexMapper.getBySrcId(billId)).map(BalanceBillindexDO::getIndexId).orElse(null);
        return Optional.ofNullable(ownerId).map(id -> {
            return Optional.ofNullable(getByOwnerIdAndType(ownerId, PayTypeEnum.RETURNPAY.getValue())).map(BalanceCheckDetailsDO::getCheckDetailid).orElse(0L);
        }).orElse(0L);
    }

    private PayDO order(Long checkId, Long orderId, Integer type, Long tenantId, LocalDateTime createTime) {
        var order = orderIndexMapper.getById(orderId);
        return Optional.ofNullable(order).map(data -> {
            //运输单
            if (OrderSrcClassEnum.isTransport(data.getIndexSrcclass())) {
                //状态是否大于等于待签收，是则说明调度过，有承运方
                if (OrderStatusEnum.WAIT_SIGN.getValue().compareTo(data.getIndexStatus()) <= 0) {
                    //线上承运方
                    if (LongUtils.notNullLong(data.getIndexSupplierid())) {
                        //可能会存在多条
                        var waybillList = orderIndexMapper.getBySrcOrderId(data.getIndexId());
                        if (CollectionUtils.isEmpty(waybillList)) {
                            return PayDO.create(checkId, 0L, type, tenantId, createTime);
                        }
                        //存在多条下游数据
                        var down = getOrderUpOrderDown(checkId, waybillList.stream().map(OrderIndexDO::getIndexId).collect(Collectors.toList()), type);
                        return PayDO.create(checkId, down, type, tenantId, createTime);
                    }
                    //线下承运方/自营 up = 自己 down = null
                    if (LongUtils.notNullLong(data.getIndexSuppliersymbolid()) || LongUtils.notNullLong(data.getIndexDriverid())) {
                        return PayDO.create(checkId, 0L, type, tenantId, createTime);
                    }
                } else {
                    //没调度时
                    return PayDO.create(checkId, 0L, type, tenantId, createTime);
                }
            }
            //客户单
            if (OrderSrcClassEnum.isOrder(data.getIndexSrcclass())) {
                //线上客户 up = srcOrderId查询checkId down = 自己
                if (CustomerTypeEnum.isOnline(data.getCustomerType())) {
                    return PayDO.create(getOrderUpOrDown(data.getIndexSrcorderid(), type), checkId, type, tenantId, createTime);
                }
                //线下/自己 客户 up = null down = 自己
                if (CustomerTypeEnum.isOffline(data.getCustomerType()) || CustomerTypeEnum.isMySelf(data.getCustomerType())) {
                    return PayDO.create(0L, checkId, type, tenantId, createTime);
                }
            }
            //拼车单
            return null;
        }).orElse(null);
    }

    private Long getOrderUpOrDown(Long orderId, Integer type) {
        if (!LongUtils.notNullLong(orderId)) {
            return 0L;
        }
        //存在则已付
        return Optional.ofNullable(getByOrderIdAndType(orderId, type)).map(BalanceCheckDetailsDO::getCheckDetailid).orElse(0L);
    }

    private Long getOrderUpOrderDown(Long upCheckId, List<Long> orderId, Integer type) {
        var list = getByOrderIdAndType(orderId, type);
        if (CollectionUtils.isEmpty(list)) {
            return 0L;
        }
        if (list.size() == 1) {
            return list.stream().findFirst().get().getCheckDetailid();
        }
        logger.error("收付款id：{},对应的下游存在多个", upCheckId);
        return 0L;
    }

    @Override
    public PayDO getByUpOrDown(Long sourceId) {
        LambdaQueryWrapper<PayDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PayDO::getUp, sourceId).or().eq(PayDO::getDown, sourceId);
        var list = payMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        if (list.size() == 1) {
            return list.stream().findAny().get();
        }
        var data = list.stream().filter(x -> x.getUp() > 0 && x.getDown() > 0).findAny().orElse(null);
        if (data == null) {
            return list.stream().findAny().get();
        }
        return data;
    }

    @Override
    public List<Long> getTime(String startTime, String endTime) {
        LambdaQueryWrapper<PayDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.ge(PayDO::getCreateTime, startTime).le(PayDO::getCreateTime, endTime);
        var list = payMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        return list.parallelStream().map(d -> {
            return d.getUp() > 0 ? d.getUp() : d.getDown();
        }).collect(Collectors.toList());
    }

    @Override
    public List<Long> getTenantIdTime(Long tenantId, String startTime, String endTime) {
        LambdaQueryWrapper<PayDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PayDO::getTenantId, tenantId).ge(PayDO::getCreateTime, startTime).le(PayDO::getCreateTime, endTime);
        var list = payMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        List<Long> result = new ArrayList<>();
        list.parallelStream().forEach(d -> {
            if (d.getUp() > 0) {
                result.add(d.getUp());
            } else {
                result.add(d.getDown());
            }
        });
        return result;
    }

    private BalanceCheckDetailsDO getByOrderIdAndType(Long orderId, Integer type) {
        LambdaQueryWrapper<BalanceCheckDetailsDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BalanceCheckDetailsDO::getCheckDetailorderid, orderId).eq(BalanceCheckDetailsDO::getCheckDetailsertype, type);
        return balanceCheckDetailsMapper.selectOne(queryWrapper);
    }

    @Override
    public List<BalanceCheckDetailsDO> getByOrderIdAndType(List<Long> orderId, Integer type) {
        LambdaQueryWrapper<BalanceCheckDetailsDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(BalanceCheckDetailsDO::getCheckDetailorderid, orderId).eq(BalanceCheckDetailsDO::getCheckDetailinvalid, 0);
        if (type != null) {
            queryWrapper.eq(BalanceCheckDetailsDO::getCheckDetailsertype, type);
        }

        return balanceCheckDetailsMapper.selectList(queryWrapper);
    }


    private BalanceCheckDetailsDO getByOwnerIdAndType(Long ownerId, Integer type) {
        return balanceCheckDetailsMapper.getReturnPayByBillId(ownerId, type);
    }

    private void batch(List<PayDO> list) {

        BatchSelectUtils.batchHandler(sqlSessionTemplate.getSqlSessionFactory(), list, PayWriteMapper.class, PayWriteMapper::add);

    }

    private List<PayDO> distinctUpAndDown(List<PayDO> list) {
        return StreamUtils.distinct(list, Comparator.comparing(b -> b.getUp() + "-" + b.getDown()), false);
    }
}
